use reqwest;
use serde_json::{json, Value};

#[tokio::test]
async fn test_login_end_to_end_idempotent() {
    let client = reqwest::Client::new();
    let base_url =
        std::env::var("TEST_SERVER_URL").unwrap_or_else(|_| "http://10.160.43.82:8383".to_string());

    // Test credentials
    let email = "alice@hillstonenet.com";
    let password = "alice123";

    // Test data structure to store tokens for multiple logins
    let mut tokens = Vec::new();

    // Perform multiple login attempts to verify idempotency
    for attempt in 1..=5 {
        println!("Login attempt #{}", attempt);

        // Step 1: Login request
        let login_response = client
            .post(&format!("{}/api/login", base_url))
            .json(&json!({
                "email": email,
                "password": password
            }))
            .send()
            .await
            .expect("Failed to send login request");

        // Verify login response status
        assert_eq!(
            login_response.status(),
            200,
            "Login attempt #{} failed with status: {}",
            attempt,
            login_response.status()
        );

        // Parse login response
        let login_data: Value = login_response
            .json()
            .await
            .expect("Failed to parse login response JSON");

        // Verify response structure
        assert!(
            login_data.get("access_token").is_some(),
            "Missing access_token in login response for attempt #{}",
            attempt
        );
        assert!(
            login_data.get("token_type").is_some(),
            "Missing token_type in login response for attempt #{}",
            attempt
        );
        assert_eq!(
            login_data["token_type"], "bearer",
            "Incorrect token_type for attempt #{}",
            attempt
        );

        let token = login_data["access_token"].as_str().unwrap();
        tokens.push(token.to_string());

        // Step 2: Verify token works with authenticated endpoint
        let user_info_response = client
            .get(&format!("{}/api/user-info", base_url))
            .header("Authorization", format!("Bearer {}", token))
            .send()
            .await
            .expect("Failed to send user info request");

        // Verify user info response status
        assert_eq!(
            user_info_response.status(),
            200,
            "User info request failed for attempt #{} with status: {}",
            attempt,
            user_info_response.status()
        );

        // Parse user info response
        let user_data: Value = user_info_response
            .json()
            .await
            .expect("Failed to parse user info response JSON");

        // Verify user data
        assert_eq!(
            user_data["email"], email,
            "Incorrect email in user info for attempt #{}",
            attempt
        );
        assert_eq!(
            user_data["id"], 2,
            "Incorrect user ID for attempt #{}",
            attempt
        );
        assert_eq!(
            user_data["is_admin"], false,
            "Incorrect admin status for attempt #{}",
            attempt
        );

        // Step 3: Verify token works with data listing endpoint
        let data_list_response = client
            .get(&format!("{}/api/data", base_url))
            .header("Authorization", format!("Bearer {}", token))
            .send()
            .await
            .expect("Failed to send data list request");

        // Verify data list response status
        assert_eq!(
            data_list_response.status(),
            200,
            "Data list request failed for attempt #{} with status: {}",
            attempt,
            data_list_response.status()
        );

        println!("Login attempt #{} successful", attempt);
    }

    // Step 4: Verify all tokens are valid (idempotency check)
    // Note: Tokens may be identical if issued within the same second due to JWT iat claim
    for (index, token) in tokens.iter().enumerate() {
        let user_info_response = client
            .get(&format!("{}/api/user-info", base_url))
            .header("Authorization", format!("Bearer {}", token))
            .send()
            .await
            .expect("Failed to send user info request for token verification");

        assert_eq!(
            user_info_response.status(),
            200,
            "Token #{} is no longer valid",
            index
        );

        // Verify user data consistency
        let user_data: Value = user_info_response
            .json()
            .await
            .expect("Failed to parse user info response");

        assert_eq!(
            user_data["email"], email,
            "Email mismatch for token #{}",
            index
        );
        assert_eq!(user_data["id"], 2, "User ID mismatch for token #{}", index);
    }

    // Step 5: Verify token structure consistency
    for (index, token) in tokens.iter().enumerate() {
        // Verify JWT token structure (3 parts separated by dots)
        let parts: Vec<&str> = token.split('.').collect();
        assert_eq!(parts.len(), 3, "Invalid JWT structure for token #{}", index);

        // Verify each part is non-empty
        for (part_idx, part) in parts.iter().enumerate() {
            assert!(
                !part.is_empty(),
                "Empty JWT part {} for token #{}",
                part_idx,
                index
            );
        }
    }

    // Step 6: Test invalid credentials
    let invalid_login_response = client
        .post(&format!("{}/api/login", base_url))
        .json(&json!({
            "email": email,
            "password": "wrongpassword"
        }))
        .send()
        .await
        .expect("Failed to send invalid login request");

    assert_eq!(
        invalid_login_response.status(),
        401,
        "Invalid login should return 401, got {}",
        invalid_login_response.status()
    );

    // Step 7: Test malformed request
    let malformed_response = client
        .post(&format!("{}/api/login", base_url))
        .json(&json!({
            "email": email
            // Missing password
        }))
        .send()
        .await
        .expect("Failed to send malformed login request");

    assert!(
        malformed_response.status() == 400 || malformed_response.status() == 422,
        "Malformed login should return 400 or 422, got {}",
        malformed_response.status()
    );

    println!("All login idempotency tests passed!");
}

#[tokio::test]
async fn test_login_with_cookie_authentication() {
    let client = reqwest::Client::new();
    let base_url =
        std::env::var("TEST_SERVER_URL").unwrap_or_else(|_| "http://10.160.43.82:8383".to_string());

    // Step 1: Login via API (simulating web UI login)
    let login_response = client
        .post(&format!("{}/api/login", base_url))
        .json(&json!({
            "email": "alice@hillstonenet.com",
            "password": "alice123"
        }))
        .send()
        .await
        .expect("Failed to send login request");

    assert_eq!(login_response.status(), 200, "Login failed");

    // Step 2: Verify explicit Bearer token works
    let login_data: Value = login_response
        .json()
        .await
        .expect("Failed to parse login response");

    let token = login_data["access_token"].as_str().unwrap();

    let bearer_response = client
        .get(&format!("{}/api/user-info", base_url))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .expect("Failed to send bearer token request");

    assert_eq!(
        bearer_response.status(),
        200,
        "Bearer token authentication failed"
    );

    println!("Bearer token authentication works correctly");
}

#[tokio::test]
async fn test_concurrent_logins() {
    let base_url =
        std::env::var("TEST_SERVER_URL").unwrap_or_else(|_| "http://10.160.43.82:8383".to_string());

    // Spawn multiple concurrent login requests
    let mut handles = Vec::new();

    for i in 0..10 {
        let url = base_url.clone();
        let handle = tokio::spawn(async move {
            let client = reqwest::Client::new();

            let response = client
                .post(&format!("{}/api/login", url))
                .json(&json!({
                    "email": "alice@hillstonenet.com",
                    "password": "alice123"
                }))
                .send()
                .await
                .expect("Failed to send login request");

            (i, response.status(), response.json::<Value>().await.ok())
        });

        handles.push(handle);
    }

    // Wait for all concurrent logins to complete
    let mut successful_logins = 0;
    for handle in handles {
        let (index, status, json_data) = handle.await.expect("Task panicked");

        assert_eq!(status, 200, "Concurrent login #{} failed", index);

        if let Some(data) = json_data {
            assert!(
                data.get("access_token").is_some(),
                "Missing token in concurrent login #{}",
                index
            );
            successful_logins += 1;
        }
    }

    assert_eq!(
        successful_logins, 10,
        "Expected 10 successful concurrent logins"
    );
    println!("All concurrent logins successful");
}
